<!DOCTYPE html>
<html lang="<%= I18n.locale || 'en' %>">
  <head>
    <meta charset="utf-8" />
    <title>&#127880; Public Lab<%= ": "+(@title || params[:action].capitalize) %></title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <%= stylesheet_link_tag "application", :media => "all" %>
    <%= javascript_include_tag "application" %>
  </head>
  <body>

<style>
html, body {
  background: #efefff;
}
#cytoscape {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 999;
}

.toppanel {
  height: auto; /* Specify a height */
  position: absolute; /* Stay in place */
  z-index: 1000; /* Stay on top */
  top: 0;
  left: 0;
  padding-left: 1em;
  padding-top: 10px; /* Place content 10px from the top */
  padding-bottom: 10px; /* Place content 10px from the bottom */
}

label{
  top: 12px;
}
input[type="range" i] {
  position: relative;
  top: 12px;
}
}

#toolbar > * {
  display: block;

}
.center-screen {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  min-height: 100vh;
  z-index: 1000000;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}


.spinner-grow {
  0%{-webkit-transform:scale(0);transform:scale(0)}
  50%{opacity:1;-webkit-transform:none;transform:none}
}

@keyframes spinner-grow{
  0%{-webkit-transform:scale(0);transform:scale(0)}
  50%{opacity:1;-webkit-transform:none;transform:none}
}

.spinner-grow{
  display:inline-block;
  width:2rem;
  height:2rem;
  vertical-align:text-bottom;
  background-color:currentColor;
  border-radius:50%;
  opacity:0;
  -webkit-animation:spinner-grow .75s linear infinite;
  animation:spinner-grow .75s linear infinite
}
</style>

<script src="/lib/cytoscape/dist/cytoscape.min.js" type="text/javascript"></script>

<script>module = {}</script>

<script type="text/javascript" src="/lib/jlouvain/jLouvain.js"></script>

<div id="toppanel" class="toppanel">
  <a class="btn btn-outline-dark" data-toggle="collapse" href="#toolbar" aria-expanded="false" aria-controls="toolbar">
      <i class="fa fa-filter"></i>
  </a>

  <div class="collapse" id="toolbar">
    <span>
      <label class="form-label" id="filterlabel" for="filter">Min. no. of Nodes</label>
      <input class="form-range" id="filter" name="filter" onchange="rangevalue.value=value" value="0" type="range" min="0">
      <span class="highlight"></span>
      <output id="rangevalue">0</output>
    </span>
    <br>
    <span>
      <select id="sort" class="form-control">
        <option id="defaultsort" value="nodes" selected>Sort By</option>
        <option value="nodes">Notes</option>
        <option value="subscribers">Subscribers</option>
      </select>
    </span>
    <br>
    <%# <span>
      <input type="text" class="form-control" id="search" placeholder="Search">
    </span> %>
  </div>

</div>

<div id="loader" class="center-screen" role="status">
  <div class="spinner-grow">
  </div>  
</div>

<div id="cytoscape"></div>

<script>
  var cy, community, c_result;

  (function() {

  var mobile = window.matchMedia("(max-width: 400px)")
  var desktop = window.matchMedia("(min-width: 400px)")


  function parse_cyto_data(data) {
    var output = {
      tagnames: [],
      nodes: [],
      edges: []
    }

    data['tags'].forEach(function(tag) {
      output.tagnames.push(tag.name);
      output.nodes.push({ data: {
        id: tag.name,
        weight: tag.count,
        href: '/tag/' + tag.name
      } });
    });
    data['edges'].forEach(function(edge) {
      if (output.tagnames.includes(edge.from) && output.tagnames.includes(edge.to)) {
        output.edges.push({
          data: {
            id: edge.from + "_" + edge.to,
            source: edge.from,
            target: edge.to,
            weight: 10 //Math.random()*10+3 // <= dummy value for jlouvain
          }
        });
      }
    });
    return output;
  }


// function to sort or filter
  function sort_or_filter(type, limit, weight) {
    $.ajax({
      url : '/tag/graph.json?limit=' + limit + '&type=' + type + '&weight=' + weight, 
      beforeSend: function(){
        $('#cytoscape').hide();
        $('#loader').show();
      },
      success:function(response){
        $('#loader').hide();
        $('#cytoscape').show();

        max_weight =  response.tags[0].count;

        setup_cytoscape(response);
      }
    });
  }

  if (mobile.matches){
    l = ("<%= params[:limit] %>" == "") ? 100 : "<%= params[:limit] %>"
  }
  else{
    l = ("<%= params[:limit] %>" == "") ? 250 : "<%= params[:limit] %>"
  }


$('#sort').change(function () {
      $('#defaultsort').attr('disabled', 'true');
      if ($(this).val() == 'nodes') {
        $('#filterlabel').text('Min. no. of Notes');
      } else {
        $('#filterlabel').text('Min. no. of Subscribers');
      }
      $('#filter').val(0);
      $('#rangevalue').val(0);

    var sort = ("<%= params[:type] %>" == "") ? $('#sort').val() : "<%= params[:type] %>"

    var weight = 0;
    sort_or_filter(sort, l, weight);

})

  $('#filter').change(function() {
    var sort = ("<%= params[:type] %>" == "") ? $('#sort').val() : "<%= params[:type] %>"

    var weight = ("<%= params[:weight] %>" == "") ? $('#filter').val() : "<%= params[:filter] %>"

    sort_or_filter(sort, l, weight);
  });

  sort_or_filter("nodes", <%= params[:limit] || 100 %>, <%= params[:weight] || 0 %>);

  function setup_cytoscape(data) {
    max_weight =  data.tags[0].count;
    $('#filter').attr('max', max_weight);


    var data = parse_cyto_data(data),
        weights = data.nodes.map(function(aa){ return aa.data.weight });

    weights = weights.sort(function(a,b){return parseFloat(a) > parseFloat(b) ? -1 : 1 });

    var max = weights[0],
        min = weights.reverse()[0];

    cy = cytoscape({
      container: document.getElementById('cytoscape'),
      elements: {
        nodes: data.nodes,
        edges: data.edges
      },

      layout: {
        name: 'cose',

        // Called on `layoutready`
        ready: function(){
          $('html').attr('background-color', 'black');
          $('#toolbar').attr('background-color', 'black');

        },

        // Called on `layoutstop`
        stop: function(){
          $('#toolbar').attr('background-color', 'black');

        },

        // Whether to animate while running the layout
        // true : Animate continuously as the layout is running
        // false : Just show the end result
        // 'end' : Animate with the end result, from the initial positions to the end positions
        animate: true,

        // Easing of the animation for animate:'end'
        animationEasing: undefined,

        // The duration of the animation for animate:'end'
        animationDuration: undefined,

        // A function that determines whether the node should be animated
        // All nodes animated by default on animate enabled
        // Non-animated nodes are positioned immediately when the layout starts
        animateFilter: function ( node, i ){ return true; },


        // The layout animates only after this many milliseconds for animate:true
        // (prevents flashing on fast runs)
        animationThreshold: 250,

        // Number of iterations between consecutive screen positions update
        refresh: 20,

        // Whether to fit the network view after when done
        fit: true,

        // Padding on fit
        padding: 30,

        // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
        boundingBox: undefined,

        // Excludes the label when calculating node bounding boxes for the layout algorithm
        nodeDimensionsIncludeLabels: false,

        // Randomize the initial positions of the nodes (true) or use existing positions (false)
        randomize: false,

        // Extra spacing between components in non-compound graphs
        componentSpacing: 100,

        // Node repulsion (non overlapping) multiplier
        nodeRepulsion: function( node ){ return 2048; },

        // Node repulsion (overlapping) multiplier
        nodeOverlap: 50,

        // Ideal edge (non nested) length
        idealEdgeLength: function( edge ){ return 100; },

        // Divisor to compute edge forces
        edgeElasticity: function( edge ){ return 32; },

        // Nesting factor (multiplier) to compute ideal edge length for nested edges
        nestingFactor: 1.2,

        // Gravity force (constant)
        gravity: 1,

        // Maximum number of iterations to perform
        numIter: 1000,

        // Initial temperature (maximum node displacement)
        initialTemp: 1000,

        // Cooling factor (how the temperature is reduced between consecutive iterations
        coolingFactor: 0.99,

        // Lower temperature threshold (below this point the layout will end)
        minTemp: 1.0,

        // Pass a reference to weaver to use threads for calculations
        weaver: false
      },
      style: [
        {
          selector: 'node',
          style: {
            'width': 'mapData(weight, ' + min + ',' + max + ', 30, 150)',
            'height': 'mapData(weight, ' + min + ',' + max + ', 30, 150)',
            'content': 'data(id)',
            'font-size': '12px',
            'text-valign': 'center',
            'text-halign': 'center',
            'text-outline-width': '2px',
            'color': '#fff',
            'overlay-padding': '6px',
            'z-index': '10'

          }
        },

        {
          selector: 'edge',
          style: {
            'curve-style': 'haystack',
            'haystack-radius': '0.5',
            'opacity': '0.4',
            'line-color': '#000',
            'width': 2,
            'overlay-padding': '3px'
          }
        }
      ]

    });

  cy.minZoom( 0.5 );
  cy.maxZoom( 20 );
  
  if (mobile.matches) { // If media query matches
    cy.on('taphold', 'node', function(e){
      cy.nodes().style('opacity', 1);
      cy.edges().style('opacity', 0.3);
      var node = e.target;
      node.connectedEdges()
        .style('line-color', '#888');
      node.connectedEdges()
        .connectedNodes()
        .style('border-color', 'white');
    });
    

    cy.on('tapend', 'node', function(e){
      cy.elements().style('opacity', 0.2);
      var node = e.target;
      node.connectedEdges()
        .style('opacity', 1)
        .style('line-color', 'magenta');
      node.connectedEdges()
        .connectedNodes()
        .style('opacity', 1)
        .style('border-color', 'magenta');
    });

    cy.on('tap', 'node', function(){
      try { // your browser may block popups
        window.open( this.data('href') );
      } catch(e){ // fall back on url change
        window.location.href = this.data('href');
      }
    });
  }

  if (desktop.matches) { // If media query matches
    cy.on('tap', 'node', function(){
      try { // your browser may block popups
        window.open( this.data('href') );
      } catch(e){ // fall back on url change
        window.location.href = this.data('href');
      }
    });
    
  }

    
    cy.on('mouseover', 'node', function(e){
      cy.elements().style('opacity', 0.2);
      var node = e.target;
      node.connectedEdges()
        .style('opacity', 1)
        .style('line-color', 'magenta');
      node.connectedEdges()
        .connectedNodes()
        .style('opacity', 1)
        .style('border-color', 'magenta');
    });

    cy.on('mouseout', 'node', function(e){
      cy.nodes().style('opacity', 1);
      cy.edges().style('opacity', 0.3);
      var node = e.target;
      node.connectedEdges()
        .style('line-color', '#888');
      node.connectedEdges()
        .connectedNodes()
        .style('border-color', 'white');
    });

    // cy.on('zoom', function(e){
    //   cy.nodes()//.style('font-size', 20/cy.zoom())
    //             .style('text-outline-width', 1.5/cy.zoom())
    //             .style('border-width', 3/cy.zoom())
    //             .style('width', 'mapData(weight / ' + cy.zoom() + ', ' + min + ',' + max + ', 30, 150)')
    //             .style('height', 'mapData(weight / ' + cy.zoom() + ', ' + min + ',' + max + ', 30, 150)')
    // });


    /*
    //attempt to use cytoscape native clustering

    var clusters = cy.elements().hca({
      mode: 'threshold',
      threshold: 15,
      attributes: [
        function( node ){ return node.data('weight'); }
      ]
    });
    */

    // https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/
    var colors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000', '#999999'];

    // clusters[0].forEach(function(n){n.style('backgroundColor','blue');})


    community = jLouvain()
      .nodes(data.tagnames)
      .edges(data.edges.map(function(a) {return a.data}))
    c_result  = community();
    // console.log(data, 'result', c_result); // https://github.com/publiclab/plots2/issues/1502

    cy.nodes().forEach(function(node) {
      var index = c_result[node.id()];
      if (index > colors.length) index = colors.length - 1;
      color = colors[index];
      node.style('backgroundColor', color);
      node.style('text-outline-color', color);
    });

  }

})();

</script>
</body>
